/**
 * 
 */
package second_assignment;

import first_assignment.Consts_1st.Movie;

/**
 * a type definition for the cluster structure. implementing basic cluster
 * functionality.
 * 
 * @author Peretz Oded, Castel Maayan, Farchy Ofir.
 */
public class ClusterType {

	// the disk array constructing the cluster.
	private DiskType[] diskArray;
	// the number of disks in the cluster.
	private int arraySize;
	// holds the index in the array of the least loaded disk (round-robin)
	private int currLeastLoadedDisk = 0;

	/**
	 * c'tor
	 * 
	 * @param size
	 *            - the size in which the cluster should be initialized.
	 */
	public ClusterType(int size, int streamLineCount) {
		arraySize = size;
		diskArray = new DiskType[size];
		for (int i = 0; i < size; ++i) {
			diskArray[i] = new DiskType(streamLineCount);
		}
	}

	/**
	 * adds a movie to the cluster. assumption (only for this assignment): movie
	 * insertion only occurs during system initialization.
	 * 
	 * @param movieId
	 *            - the movie to add (unique id)
	 * @return the disk number where the movie was put.
	 */
	public int AddMovie(String movieId) {
	    UpdateLeastLoadedDiskIndex();
		if (currLeastLoadedDisk == Consts_2nd.eINVALID) return Consts_2nd.eINVALID;
		int tmpDisk = currLeastLoadedDisk;
		while (!diskArray[currLeastLoadedDisk].AddMovie(movieId)) {
			currLeastLoadedDisk = (currLeastLoadedDisk + 1) % arraySize;
			if (currLeastLoadedDisk == tmpDisk)
				return Consts_2nd.eINVALID;
		}
		tmpDisk = currLeastLoadedDisk;
		return tmpDisk;
	}

	/**
	 * marks a given movie as occupied (being used by a client)
	 * 
	 * @param movieId
	 *            - the movie to occupy.
	 * @return the disk from which the movie was occupied, or error code.
	 */
	public int OccupyMovie(String movieId) {
	    int index = Consts_2nd.eINVALID;
	    int maxNumOfStream = 0;
		for (int i = 0; i < arraySize; ++i) {
			if (diskArray[i].Contains(movieId) &&
			        maxNumOfStream < diskArray[i].NumberOfStreamsLeft()) {
			    maxNumOfStream = diskArray[i].NumberOfStreamsLeft();
			    index = i;
			}
		}
		if (index != Consts_2nd.eINVALID && diskArray[index].OccupyMovie(movieId)) {
		    return index;
		}
		return Consts_2nd.eINVALID;
	}

	/**
	 * clears the given movie on the given for further client use.
	 * 
	 * @param movieId
	 *            - the movie to clear status for.
	 * @param disk
	 *            - the disk on which the release will be performed.
	 */
	public void ReleaseMovie(String movieName, int disk) {
		diskArray[disk].ReleaseMovie(movieName);
	}

	public int AmountOfCopies() {
		int copiesCount = 0;
		for (DiskType disk : diskArray) {
			copiesCount += disk.AmountOfCopies();
		}
		return copiesCount;
	}
	
	public boolean DeleteMovie(String movieName){
		for (int i = 0; i < arraySize; ++i){
			if (diskArray[i].DeleteMovie(movieName)){
				return true;
			}
		}
		return false; // movie was not found
	}

	private void UpdateLeastLoadedDiskIndex() {
		int currLeastLoadedStream = 0;
		currLeastLoadedDisk = 0;
		for (int i = 0; i < arraySize; ++i){
			if (diskArray[i].AmountOfCopies() < diskArray[currLeastLoadedDisk].AmountOfCopies())
				currLeastLoadedDisk = i;
            if (diskArray[i].NumberOfStreamsLeft() > diskArray[currLeastLoadedStream].NumberOfStreamsLeft())
                currLeastLoadedStream = i;
        }
		if (diskArray[currLeastLoadedDisk].AmountOfCopies() == Consts_2nd.DiskCapacity) {
		    if (diskArray[currLeastLoadedStream].NumberOfStreamsLeft() == 0) {
		        currLeastLoadedDisk = Consts_2nd.eINVALID;
		    }
		    else {
		        currLeastLoadedDisk = currLeastLoadedStream;
		    }
        }
	}

	public int ForceMovieInsertion(Movie movieToAdd) {
	    UpdateLeastLoadedDiskIndex();
		if (0 >= movieToAdd.copies || currLeastLoadedDisk == Consts_2nd.eINVALID) return 0;
		int copiesStored = 0;
		for (int j=0,i=currLeastLoadedDisk; j<arraySize; ++j, i=(i+1)%arraySize)
		{
			if (diskArray[i].AddMovie(movieToAdd.movieName)) {
				++copiesStored;
				break;
			}
			else if (!diskArray[i].Contains(movieToAdd.movieName)) {
				String movieToMove = diskArray[i].getUniqueMovie(diskArray[currLeastLoadedDisk]);
				if (diskArray[currLeastLoadedDisk].AddMovie(movieToMove)) {
				    if (diskArray[i].DeleteMovie(movieToMove)) {
				        if (diskArray[i].AddMovie(movieToAdd.movieName)) {
				            ++copiesStored;
            				break;
				        }
				    }
				    else {
				        diskArray[currLeastLoadedDisk].DeleteMovie(movieToMove);
				    }
				}
			}
		}
		return copiesStored;
	}

	public Boolean Contains(String movieName) {
		for (int i = 0; i < arraySize; ++i){
			if (diskArray[i].Contains(movieName)) return true;
		}
		return false;
	}

	public int CountCopies(String movieName) {
		int counter = 0;
		for (int i = 0; i < arraySize; ++i){
			if (diskArray[i].Contains(movieName)) {
				++counter;
			}
		}
		return counter;
	}
	
	public DiskType getNonFullDisk() {
		for (int i = 0; i < arraySize; ++i){
			if (diskArray[i].AmountOfCopies() < Consts_2nd.DiskCapacity) return diskArray[i];
		}
		return null;
	}

	public DiskType CanAccommodateMovie(String movieName) {
		// returns a disk that doesn't contain the movie (if there is any).
		// we work under the assumption that all disks are full,
		// if not than the previous stage (reordering the disks inside the cluster)
		// would insert this movie.
		for (int i = 0; i < arraySize; ++i){
			if (!diskArray[i].Contains(movieName)) return diskArray[i];
		}
		return null;
	}

	public int DeleteMovieCopy(String movieCopyToDel) {
	    UpdateLeastLoadedDiskIndex();
	    int j = currLeastLoadedDisk;
	    if (j < 0) j = 0;
		for (int i = 0; i < arraySize; ++i){
			if (diskArray[j].DeleteMovie(movieCopyToDel) == true){
				return j;
			}
			j = (j+1) % arraySize;
		}
		return -1; // movie was not found
	}

	public boolean AddMovie(Movie movie, int disk) {
		if (diskArray[disk].AddMovie(movie.movieName) == false){
			if (ForceMovieInsertion(movie) == 1){
				return true;
			}
			return false;
		}
		return true;
	}
}